﻿#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using PHMEE.Localization;
using Volo.Abp;
using Volo.Abp.Domain.Services;

namespace PHMEE.EnergyEfficiency;

/// <summary>
/// 基于 Flow Meter 数据计算油耗
/// </summary>
public class FuelConsumptionCalculator : DomainService
{
    private readonly IFlowMeterTdRepository _flowMeterRepository;
    private readonly IVesselRepository _vesselRepository;
    private readonly IFlowmeterTemplateRepository _flowMeterTemplateRepository;
    private readonly IStringLocalizer<EnergyEfficiencyResource> _localizer;

    public FuelConsumptionCalculator(IFlowMeterTdRepository flowMeterRepository,
        IVesselRepository vesselRepository,
        IFlowmeterTemplateRepository flowMeterTemplateRepository,
        IStringLocalizer<EnergyEfficiencyResource> localizer)
    {
        _flowMeterRepository = flowMeterRepository;
        _vesselRepository = vesselRepository;
        _flowMeterTemplateRepository = flowMeterTemplateRepository;
        _localizer = localizer;
    }

    /// <summary>
    /// 全船某个时间段内对各油品的消耗
    /// </summary>
    /// <param name="vesselId"></param>
    /// <param name="startDateTimeUtc"></param>
    /// <param name="endDateTimeUtc"></param>
    /// <returns></returns>
    public async Task<Dictionary<FuelType, float>> GetFuelConsumptionForDateTimePeriodAsync(Guid vesselId,
        DateTime startDateTimeUtc,
        DateTime endDateTimeUtc)
    {
        var (_, _, _, totalConsumptions) = await GetFuelConsumptionForDateTimePeriodGroupedByUnitAsync(vesselId, startDateTimeUtc, endDateTimeUtc);
        return totalConsumptions;
    }

    /// <summary>
    /// 全船、主机、副机、锅炉，某个时间段内对各油品的消耗，按机组分组
    /// </summary>
    /// <param name="vesselId"></param>
    /// <param name="startDateTimeUtc"></param>
    /// <param name="endDateTimeUtc"></param>
    /// <returns></returns>
    public async Task<(Dictionary<FuelType, float> meConsumptions,
            Dictionary<FuelType, float> aeConsumptions,
            Dictionary<FuelType, float> boConsumptions,
            Dictionary<FuelType, float> totalConsumptions)>
        GetFuelConsumptionForDateTimePeriodGroupedByUnitAsync(Guid vesselId,
            DateTime startDateTimeUtc,
            DateTime endDateTimeUtc)
    {
        var flowMeterConfig = await GetFlowMeterConfigAsync(vesselId);
        // 是否是嵌套安装主机副机流量计的船舶
        bool isMeAeNestedInstallation = flowMeterConfig.IsMeAeNestedInstallation();

        // 主机 或 主副机嵌套
        Dictionary<FuelType, float> meOrMeAeFuelConsumptions;

        // 如果是嵌套安装主机副机流量计的船舶，那么 meOrMeAeFuelConsumptions 实际上是“主副机总油耗”
        if (isMeAeNestedInstallation)
        {
            string? meAeInletFlowMeterName = flowMeterConfig.GetFlowMeterName(DeviceFuelEnum.MainAndAssist, true);
            string? meAeReturnFlowMeterName = flowMeterConfig.GetFlowMeterName(DeviceFuelEnum.MainAndAssist, false);
            meOrMeAeFuelConsumptions = await GetFlowMeterPairFuelConsumptionForDateTimePeriodAsync(
                vesselId,
                meAeInletFlowMeterName, meAeReturnFlowMeterName,
                startDateTimeUtc, endDateTimeUtc);
        }
        else
        {
            string? meInletFlowMeterName = flowMeterConfig.GetFlowMeterName(DeviceFuelEnum.MainEngine, true);
            string? meReturnFlowMeterName = flowMeterConfig.GetFlowMeterName(DeviceFuelEnum.MainEngine, false);
            meOrMeAeFuelConsumptions = await GetFlowMeterPairFuelConsumptionForDateTimePeriodAsync(
                vesselId,
                meInletFlowMeterName, meReturnFlowMeterName,
                startDateTimeUtc, endDateTimeUtc);
        }

        // 副机
        string? aeInletFlowMeterName = flowMeterConfig.GetFlowMeterName(DeviceFuelEnum.AuxiliaryEngine, true);
        string? aeReturnFlowMeterName = flowMeterConfig.GetFlowMeterName(DeviceFuelEnum.AuxiliaryEngine, false);
        Dictionary<FuelType, float> aeFuelConsumptions = await GetFlowMeterPairFuelConsumptionForDateTimePeriodAsync(
            vesselId,
            aeInletFlowMeterName, aeReturnFlowMeterName,
            startDateTimeUtc, endDateTimeUtc);

        // 锅炉
        var boilerInletFlowMeter = flowMeterConfig.GetFlowMeterName(DeviceFuelEnum.Boiler, true);
        var boilerReturnFlowMeter = flowMeterConfig.GetFlowMeterName(DeviceFuelEnum.Boiler, false);
        Dictionary<FuelType, float> boFuelConsumptions = await GetFlowMeterPairFuelConsumptionForDateTimePeriodAsync(
            vesselId,
            boilerInletFlowMeter, boilerReturnFlowMeter,
            startDateTimeUtc, endDateTimeUtc);

        // 主机
        Dictionary<FuelType, float> meFuelConsumptions = meOrMeAeFuelConsumptions;
        // 如果是嵌套安装主机副机流量计的船舶，那么 meOrMeAeFuelConsumptions 实际上是“主副机总油耗”
        if (isMeAeNestedInstallation)
        {
            // 要得到真正的主机油耗，还需要减去副机油耗
            meFuelConsumptions = FuelConsumptionDictionaryMinus(meOrMeAeFuelConsumptions, aeFuelConsumptions);
        }

        var combinedFuelConsumptionDictionaries = CombineFuelConsumptionDictionaries(
            new List<Dictionary<FuelType, float>>
            {
                meFuelConsumptions,
                aeFuelConsumptions,
                boFuelConsumptions
            });

        return (meFuelConsumptions, aeFuelConsumptions, boFuelConsumptions, combinedFuelConsumptionDictionaries);
    }
    
    

    /// <summary>
    /// 组合多个油耗字典
    /// </summary>
    /// <param name="dictionaries"></param>
    /// <returns></returns>
    private static Dictionary<FuelType, float> CombineFuelConsumptionDictionaries(
        IEnumerable<Dictionary<FuelType, float>> dictionaries)
    {
        var totalFuelConsumption = new Dictionary<FuelType, float>();

        foreach (var dictionary in dictionaries)
        {
            foreach (var pair in dictionary)
            {
                if (totalFuelConsumption.ContainsKey(pair.Key))
                {
                    totalFuelConsumption[pair.Key] += pair.Value;
                }
                else
                {
                    totalFuelConsumption.Add(pair.Key, pair.Value);
                }
            }
        }

        return totalFuelConsumption;
    }

    /// <summary>
    /// 减法运算
    /// </summary>
    /// <param name="minuend"></param>
    /// <param name="subtrahend"></param>
    /// <returns></returns>
    public static Dictionary<FuelType, float> FuelConsumptionDictionaryMinus(
        Dictionary<FuelType, float> minuend, Dictionary<FuelType, float> subtrahend)
    {
        var result = new Dictionary<FuelType, float>();

        // 处理 minuend 中的每个 FuelType
        foreach (var pair in minuend)
        {
            if (subtrahend.TryGetValue(pair.Key, out var value))
            {
                // 如果 subtrahend 中也有这个 FuelType，执行减法操作
                result.Add(pair.Key, pair.Value - value);
            }
            else
            {
                // 如果 subtrahend 中没有这个 FuelType，保留 minuend 中的值
                result.Add(pair.Key, pair.Value);
            }
        }

        // 处理 subtrahend 中有，但 minuend 中没有的 FuelType
        foreach (var pair in subtrahend)
        {
            if (!minuend.ContainsKey(pair.Key))
            {
                // 如果 minuend 中没有这个 FuelType，将其值作为负数加入结果
                result.Add(pair.Key, -pair.Value);
            }
        }

        return result;
    }

    // 查询某一对进油回油流量计计算的某个时间段内的油耗
    private async Task<Dictionary<FuelType, float>> GetFlowMeterPairFuelConsumptionForDateTimePeriodAsync(Guid vesselId,
        string? inletFlowMeterName, string? returnFlowMeterName,
        DateTime startDateTimeUtc, DateTime endDateTimeUtc)
    {
        // 如果连进油流量计的配置都没有，那么就直接不计算油耗
        if (string.IsNullOrWhiteSpace(inletFlowMeterName))
        {
            return new Dictionary<FuelType, float>();
        }

        List<FlowMeterOilValueTde> inletRecords = await _flowMeterRepository.GetListAsync(vesselId, startDateTimeUtc,
            endDateTimeUtc, inletFlowMeterName, TdOrder.Asc, TdRangeEdge.CloseOpen);

        List<FlowMeterOilValueTde> returnRecords = [];

        if (!string.IsNullOrWhiteSpace(returnFlowMeterName))
        {
            returnRecords = await _flowMeterRepository.GetListAsync(vesselId, startDateTimeUtc, endDateTimeUtc,
                returnFlowMeterName, TdOrder.Asc, TdRangeEdge.CloseOpen);
        }

        return CalculateFuelConsumption(inletRecords, returnRecords, startDateTimeUtc, endDateTimeUtc);
    }

    // /// <summary>
    // /// 查询副机某个时间段内对各种油品的油耗
    // /// </summary>
    // /// <param name="flowMeterConfig"></param>
    // /// <param name="vesselId"></param>
    // /// <param name="startDateTimeUtc"></param>
    // /// <param name="endDateTimeUtc"></param>
    // /// <returns></returns>
    // public async Task<Dictionary<FuelType, float>> GetAEFuelConsumptionForDateTimePeriodAsync(
    //     FuelFlowMeterConfig flowMeterConfig, Guid vesselId, DateTime startDateTimeUtc, DateTime endDateTimeUtc)
    // {
    //     // 如果连进油流量计的配置都没有，那么就直接不计算油耗
    //     if (!flowMeterConfig.MEInlet)
    //     {
    //         return new Dictionary<FuelType, float>();
    //     }
    //     
    //     var aeInRecords = await _flowMeterRepository.GetAeInListAsync(vesselId, startDateTimeUtc, endDateTimeUtc, TdOrder.Asc, TdRangeEdge.CloseOpen);
    //     
    //     List<FlowMeterOilValueTde> aeOutRecords = [];
    //     if (flowMeterConfig.MEOutlet)
    //     {
    //         // 如果存在回油流量计，那么就查询出油数据
    //         aeOutRecords = await _flowMeterRepository.GetAeOutListAsync(vesselId, startDateTimeUtc, endDateTimeUtc, TdOrder.Asc, TdRangeEdge.CloseOpen);
    //     }
    //
    //     return CalculateFuelConsumption(aeInRecords, aeOutRecords, startDateTimeUtc, endDateTimeUtc);
    // }
    //
    // /// <summary>
    // /// 查询主机某个时间段内对各种油品的油耗
    // /// </summary>
    // /// <param name="flowMeterConfig"></param>
    // /// <param name="vesselId"></param>
    // /// <param name="startDateTimeUtc"></param>
    // /// <param name="endDateTimeUtc"></param>
    // /// <returns></returns>
    // public async Task<Dictionary<FuelType, float>> GetMEFuelConsumptionForDateTimePeriodAsync(
    //     FuelFlowMeterConfig flowMeterConfig, Guid vesselId, DateTime startDateTimeUtc, DateTime endDateTimeUtc)
    // {
    //     // 如果连进油流量计的配置都没有，那么就直接不计算油耗
    //     if (!flowMeterConfig.MEInlet)
    //     {
    //         return new Dictionary<FuelType, float>();
    //     }
    //     
    //     var meInRecords = await _flowMeterRepository.GetMeInListAsync(vesselId, startDateTimeUtc, endDateTimeUtc, TdOrder.Asc, TdRangeEdge.CloseOpen);
    //
    //     List<FlowMeterOilValueTde> meOutRecords = [];
    //     if (flowMeterConfig.MEOutlet)
    //     {
    //         // 如果存在回油流量计，那么就查询出油数据
    //         meOutRecords = await _flowMeterRepository.GetMeOutListAsync(vesselId, startDateTimeUtc, endDateTimeUtc, TdOrder.Asc, TdRangeEdge.CloseOpen);
    //     }
    //
    //     return CalculateFuelConsumption(meInRecords, meOutRecords, startDateTimeUtc, endDateTimeUtc);
    // }
    //
    // /// <summary>
    // /// 查询锅炉某个时间段内对各种油品的油耗
    // /// </summary>
    // /// <param name="flowMeterConfig"></param>
    // /// <param name="vesselId"></param>
    // /// <param name="startDateTimeUtc"></param>
    // /// <param name="endDateTimeUtc"></param>
    // /// <returns></returns>
    // public async Task<Dictionary<FuelType, float>> GetBoilerFuelConsumptionForDateTimePeriodAsync(
    //     FuelFlowMeterConfig flowMeterConfig, Guid vesselId, DateTime startDateTimeUtc, DateTime endDateTimeUtc)
    // {
    //     // 如果连进油流量计的配置都没有，那么就直接不计算油耗
    //     if (!flowMeterConfig.BoilerInlet)
    //     {
    //         return new Dictionary<FuelType, float>();
    //     }
    //     
    //     var boilerInRecords = await _flowMeterRepository.GetBoInListAsync(vesselId, startDateTimeUtc, endDateTimeUtc, TdOrder.Asc, TdRangeEdge.CloseOpen);
    //
    //     List<FlowMeterOilValueTde> boilerOutRecords = [];
    //     if (flowMeterConfig.BoilerOutlet)
    //     {
    //         // 如果存在回油流量计，那么就查询出油数据
    //         boilerOutRecords = await _flowMeterRepository.GetBoOutListAsync(vesselId, startDateTimeUtc, endDateTimeUtc, TdOrder.Asc, TdRangeEdge.CloseOpen);
    //     }
    //
    //     return CalculateFuelConsumption(boilerInRecords, boilerOutRecords, startDateTimeUtc, endDateTimeUtc);
    // }

    /// <summary>
    /// 计算 inRecords 和 outRecords 之间的油耗
    /// 以 startDateTimeUtc 为起点，endDateTimeUtc 为终点，如果 inRecords 和 outRecords 中有数据不在这个范围内，则不计算
    /// TODO: 待测试
    /// </summary>
    /// <param name="inRecords"></param>
    /// <param name="outRecords"></param>
    /// <param name="startDateTimeUtc"></param>
    /// <param name="endDateTimeUtc"></param>
    /// <returns></returns>
    public static Dictionary<FuelType, float> CalculateFuelConsumption(List<FlowMeterOilValueTde> inRecords,
        List<FlowMeterOilValueTde> outRecords, DateTime startDateTimeUtc, DateTime endDateTimeUtc)
    {
        // 创建一个字典来存储每种油品的油耗
        var fuelConsumptionByType = new Dictionary<FuelType, float>();

        // 油耗需要根据油品区分
        // 油品只存在于进油的 List 中
        // boilerIn 是根据 Ts ASC 排序后的
        // 首先根据 inRecords，计算出油品切换的时间点，以此来顺序的将整个时间段划分为 1 到 n 个时间段，每个时间段内只有一种油品
        // 然后根据每个时间段内的进油和回油计算出每种油品的消耗
        // 传入的时间范围是 [startDateTimeUtc, endDateTimeUtc)，要以 startDateTimeUtc 为起点，endDateTimeUtc 为终点
        // 最后根据 n 个时间段每个时间段的油品，按照油品分类统计油耗
        // 油耗的计算方式是：每种油品的消耗 = 每种油品的进油 - 每种油品的回油
        // 每种油品的进油就是这个油品时间段内的 OilValue 相加，回油也是

        #region 根据进油数据，按照油品划分时间段

        List<FuelTypeDateTimeRange> timeRanges = [];

        // 记录上一个油品的时间点
        DateTime lastFuelTypeTime = startDateTimeUtc;

        // 记录上一个油品
        FuelType? lastFuelType = null;

        foreach (var record in inRecords)
        {
            if (lastFuelType is null)
            {
                lastFuelType = record.FuelType;
                continue;
            }

            if (record.FuelType != lastFuelType)
            {
                timeRanges.Add(new FuelTypeDateTimeRange
                {
                    FuelType = lastFuelType.Value,
                    StartTimeUtc = lastFuelTypeTime,
                    EndTimeUtc = record.Ts
                });
                lastFuelType = record.FuelType;
                lastFuelTypeTime = record.Ts;
            }
        }

        if (lastFuelType is null)
        {
            // 如果循环完毕，lastFuelType 仍然为 null，说明没有数据
            return fuelConsumptionByType;
        }

        // 添加最后一个时间段
        timeRanges.Add(new FuelTypeDateTimeRange
        {
            FuelType = lastFuelType.Value,
            StartTimeUtc = lastFuelTypeTime,
            EndTimeUtc = endDateTimeUtc
        });

        #endregion 根据进油数据，按照油品划分时间段

        // 根据时间段计算油耗

        foreach (var timeRange in timeRanges)
        {
            // 获取这个时间段内的进油和回油数据
            var inletRecords = inRecords
                .Where(record => record.Ts >= timeRange.StartTimeUtc && record.Ts < timeRange.EndTimeUtc).ToList();
            var outletRecords = outRecords
                .Where(record => record.Ts >= timeRange.StartTimeUtc && record.Ts < timeRange.EndTimeUtc).ToList();

            // 计算这个时间段内的油耗
            float consumedFuel = inletRecords.Sum(record => record.OilValue) -
                                 outletRecords.Sum(record => record.OilValue);

            if (!fuelConsumptionByType.TryAdd(timeRange.FuelType, consumedFuel))
            {
                fuelConsumptionByType[timeRange.FuelType] += consumedFuel;
            }
        }

        return fuelConsumptionByType;
    }

    /// <summary>
    /// 获取船舶的流量计配置
    /// </summary>
    /// <param name="vesselId"></param>
    /// <returns></returns>
    /// <exception cref="UserFriendlyException"></exception>
    public async Task<FuelFlowMeterConfig> GetFlowMeterConfigAsync(Guid vesselId)
    {
        FlowMeterModeEnum? flowMeterMode = await _vesselRepository.GetTemplateModeByVesselIdAsync(vesselId);
        // 未配置流量计安装模式
        if (flowMeterMode is null)
        {
            throw new UserFriendlyException(_localizer[PHMEEEnergyEfficiencyErrorCodes.FlowMeterModeNotConfigured],
                PHMEEEnergyEfficiencyErrorCodes.FlowMeterModeNotConfigured);
        }

        List<FlowmeterTemplate> flowMeterTemplates =
            await _flowMeterTemplateRepository.GetTemplatesByModeAsync(flowMeterMode.Value);

        return new FuelFlowMeterConfig
        {
            FlowMeterMode = flowMeterMode.Value,
            FlowMeterTemplates = flowMeterTemplates
        };
    }
}

public class FuelTypeDateTimeRange
{
    public FuelType FuelType { get; set; }

    /// <summary>
    /// 闭区间（含）
    /// </summary>
    /// <value></value>
    public DateTime StartTimeUtc { get; set; }

    /// <summary>
    /// 开区间（不含）
    /// </summary>
    /// <value></value>
    public DateTime EndTimeUtc { get; set; }
}